//
//  Copyright (C) 1998-2007 J. Andrew McLaughlin
//  Copyright (C) 2009 Vadim Ushakov
// 
//  This program is free software; you can redistribute it and/or modify it
//  under the terms of the GNU General Public License as published by the Free
//  Software Foundation; either version 2 of the License, or (at your option)
//  any later version.
// 
//  This program is distributed in the hope that it will be useful, but
//  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
//  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
//  for more details.
//  
//  You should have received a copy of the GNU General Public License along
//  with this program; if not, write to the Free Software Foundation, Inc.,
//  59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//
//  kernelApi.c
//

// This is the part of the kernel's API that sorts out which functions
// get called from external locations.

#include "kernelApi.h"
#include "kernelDebug.h"
#include "kernelDisk.h"
#include "kernelEncrypt.h"
#include "kernelEnvironment.h"
#include "kernelError.h"
#include "kernelFile.h"
#include "kernelFileStream.h"
#include "kernelFilesystem.h"
#include "kernelKeyboard.h"
#include "kernelLoader.h"
#include "kernelMemory.h"
#include "kernelMisc.h"
#include "kernelMultitasker.h"
#include "kernelNetwork.h"
#include "kernelNetworkDevice.h"
#include "kernelParameters.h"
#include "kernelPerformanceCounter.h"
#include "kernelProcessorX86.h"
#include "kernelRandom.h"
#include "kernelRtc.h"
#include "kernelShutdown.h"
#include "kernelSysTimer.h"
#include "kernelText.h"
#include "kernelUser.h"
#include "kernelWindow.h"
#include "kernelWindowComponent.h"

#include <sys/api_index.h>
#include "kernelApi_api_disk.h"
#include "kernelApi_api_environment.h"
#include "kernelApi_api_file.h"
#include "kernelApi_api_filesystem.h"
#include "kernelApi_api_graphics.h"
#include "kernelApi_api_loader.h"
#include "kernelApi_api_memory.h"
#include "kernelApi_api_misc.h"
#include "kernelApi_api_multitasker.h"
#include "kernelApi_api_network.h"
#include "kernelApi_api_performance.h"
#include "kernelApi_api_random.h"
#include "kernelApi_api_rtc.h"
#include "kernelApi_api_text.h"
#include "kernelApi_api_user.h"
#include "kernelApi_api_window.h"
#include "kernelApiIndex.h"

static int pcApiCalls = -1;
static int pcInitialized = 0;


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
//
//  Below here, the functions are exported for external use
//
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


void kernelApi(unsigned CS __attribute__((unused)), kernelArgVector *argList)
{
	// This is the initial entry point for the kernel's API.  This
	// function will be first the recipient of all calls to the global
	// call gate.  This function will pass a pointer to the rest of the
	// arguments to the processCall function that does all the real work.
	// This funcion does the far return.

	int64_t status = 0;
	int statusLo;
	int statusHi;
	unsigned stackAddress = 0;
	int argCount = 0;
	int functionIndex = 0;
	unsigned* argVector = 0;
	kernelFunctionIndex *functionEntry = NULL;
	int currentProc = 0;
	int currentPriv = 0;
	int64_t (*functionPointer)() = NULL;
	int count;
#if defined(DEBUG)
	extern kernelSymbol *kernelSymbols;
	extern int kernelNumberSymbols;
	char *symbol = "unknown";
#endif // defined(DEBUG)

	kernelProcessorApiEnter(stackAddress);

	// Check arg
	if (argList == NULL)
	{
		kernelError(kernel_error, "No args supplied to API call");
		status = ERR_NULLPARAMETER;
		goto out;
	}

	// Which function number are we being asked to call?
	functionIndex = argList->functionIndex;

	// How many parameters are there?
	argCount = argList->argCount;

	if (argCount < 0 || argCount > API_MAX_ARGS)
	{
		kernelError(kernel_error, "Illegal number of arguments (%d) to API call %d", argCount, functionIndex);
		status = ERR_ARGUMENTCOUNT;
		goto out;
	}

	unsigned TableIndex = (unsigned)functionIndex / 1000;
	unsigned EntryIndex = (unsigned)functionIndex % 1000;

	if (TableIndex > MAX_FUNCTION_TABLE
	||  functionTables[TableIndex].table == NULL
	||  functionTables[TableIndex].size <= EntryIndex)
	{
		kernelError(kernel_error, "Illegal function number (%d) in API call", functionIndex);
		status = ERR_NOSUCHENTRY;
		goto out;
	}

	functionEntry = &functionTables[TableIndex].table[EntryIndex];

	if (functionEntry == NULL
	||  functionEntry->functionIndex != functionIndex)
	{
		kernelError(kernel_error, "Invalid entry for function %d in API table", functionIndex);
		status = ERR_NOSUCHFUNCTION;
		goto out;
	}

	// Do the number of args match the number expected?
	if (argCount != functionEntry->argCount)
	{
		kernelError(kernel_error, "Incorrect number of arguments (%d) to API "
			"call %d (%d)", argCount, functionEntry->functionIndex,
			functionEntry->argCount);
		status = ERR_ARGUMENTCOUNT;
		goto out;
	}

	// Does the caller have the adequate privilege level to call this
	// function?
	currentProc = kernelMultitaskerGetCurrentProcessId();
	currentPriv = kernelMultitaskerGetProcessPrivilege(currentProc);
	if (currentPriv < 0)
	{
		kernelError(kernel_error, "Couldn't determine current privilege level "
			"in call to API function %d", functionEntry->functionIndex);
		status = currentPriv;
		goto out;
	}
	else if (currentPriv > functionEntry->privilege)
	{
		kernelError(kernel_error, "Insufficient privilege to invoke API "
			"function %d", functionEntry->functionIndex);
		status = ERR_PERMISSION;
		goto out;
	}

	if (functionEntry->flags & API_FUNC_NOT_SUPPORTED)
	{
		kernelError(kernel_error, "API function %d is not supported", functionEntry->functionIndex);
		status = ERR_NOTIMPLEMENTED;
		goto out;
	}

	if (functionEntry->flags & API_FUNC_DEPRECATED)
	{
		kernelError(kernel_error, "API function %d is DEPRECATED", functionEntry->functionIndex);
	}

	// Make 'functionPointer' equal the address of the requested kernel
	// function.
	functionPointer = functionEntry->functionPointer;

	argVector = argList->argVector;
	if (!argVector && argCount > 0)
	{
		kernelError(kernel_error, "Incorrect argVector in call to API function %d", functionIndex);
		status = ERR_ARGUMENTCOUNT;
		goto out;
	}

	if (functionEntry->argSize % sizeof(unsigned) != 0)
	{
		kernelError(kernel_error, "Internal error in API table for function %d: argSize value is %d", functionIndex, functionEntry->argSize);
		status = ERR_ARGUMENTCOUNT;
		goto out;
	}
	int wordCount = functionEntry->argSize / sizeof(unsigned);
	if (wordCount < argCount)
	{
		kernelError(kernel_error, "Internal error in API table for function %d: wordCount < argCount (%d < %d)", functionIndex, wordCount, argCount);
		status = ERR_ARGUMENTCOUNT;
		goto out;
	}

#if defined(DEBUG)
	if (kernelSymbols)
	{
		for (count = 0; count < kernelNumberSymbols; count ++)
		{
			if (kernelSymbols[count].address == (unsigned) functionPointer)
			{
				symbol = kernelSymbols[count].symbol;
				break;
			}
		}
	}
	kernelDebug(debug_api, "Kernel API function %d (%s), %d args ", functionIndex, symbol, argCount);
	for (count = 0; count < wordCount; count ++)
		kernelDebug(debug_api, "word %d=%p", count, (void *) argVector[count]);
#endif // defined(DEBUG)

	for (count = wordCount - 1; count >= 0; count --)
		kernelProcessorPush(argVector[count]);

	status = functionPointer();

	for (count = wordCount - 1; count >= 0; count --)
		kernelProcessorPop(argVector[count]);

	kernelSendEntropyValue((unsigned)argCount);
	if (!pcInitialized)
	{
		pcInitialized = 1;
		pcApiCalls = kernelPerformanceCounterRegister("kernel.api_calls");
	}
	kernelPerformanceCounterInc1(pcApiCalls);

#if defined(DEBUG)
	kernelDebug(debug_api, "ret=%d", status);
#endif

	out:
	statusLo = (int) status;
	statusHi = (int) (status >> 32);
	kernelProcessorApiExit(stackAddress, statusLo, statusHi);
}
